/*
 * Licensed to the 南京汇海交通科技有限公司
 *
 * Copyright (c) 2025.
 * All rights reserved.
 *
 * 本软件属南京汇海交通科技有限公司所有，在未获得南京汇海交通科技有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.chframework.boot.web.exception.handler;

import cn.hutool.core.date.DatePattern;
import com.chframework.boot.core.response.result.ApiResult;
import com.chframework.boot.core.response.result.OpenApiResult;
import com.chframework.boot.core.response.status.ApiStatusEnum;
import com.chframework.common.constant.ApplicationConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;

/**
 * 请求转发层 - 全局异常处理类，捕获异常后 返回JSON格式报文
 * 如果使用了 注解@ControllerAdvice，则需要搭配注解@ResponseBody才能返回JSON格式报文
 */
@Slf4j
@RestControllerAdvice
public class ApplicationExceptionHandler extends RequestBodyAdviceAdapter implements ResponseBodyAdvice<OpenApiResult> {

    /**
     * 应用到所有使用了@RequestMapping注解的方法，在其执行之前初始化数据绑定器
     */
    @InitBinder
    public void initWebBinder(WebDataBinder binder) {
        log.debug("开始初始化数据绑定器");
        // 把String类型的参数先trim再绑定
        binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
        // 把Date类型的参数先格式化再绑定
        binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat(DatePattern.NORM_DATETIME_PATTERN), false));
    }

    /**
     * 把值绑定到Model中，使全局@RequestMapping可以获取到该值
     */
    @ModelAttribute
    public void addAttribute(Model model) {
        log.debug("开始绑定初始化模型对象");
        // model.addAttribute(ApplicationConstant.RESPONSE_IDENTITY, IdUtil.objectId());
    }

    /*
     * 全局异常捕捉处理，返回JSON格式类型
     *
    @ExceptionHandler(value = {ApplicationException.class}) // 指定拦截异常的类型，由请求转发层异常类统一处理异常信息
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 自定义浏览器返回状态码
    public ApiResult<Serializable> jsonExceptionHandler(ApplicationException exception) {
        StringJoiner joiner = new StringJoiner("");
        ApiResult<Serializable> result = ApiHelper.buildResult(exception.getMessage(), joiner);
        log.error(joiner.toString(), exception);
        // 将异常响应结果返回
        return result;
    }*/

    /**
     * 全局异常捕捉处理，返回JSON格式类型
     */
    @ExceptionHandler
    public ApiResult<Object> jsonExceptionHandler(Exception exception) {
        return ExceptionConverter.convert(exception);
    }

    //region extends RequestBodyAdviceAdapter

    /**
     * Invoked first to determine if this interceptor applies.
     * (supports判断方法，会在beforeBodyRead 和 afterBodyRead方法前都触发一次，都会对应根据返回值要不要继续执行beforeBodyRead 或afterBodyRead。)
     *
     * @param methodParameter the method parameter
     * @param targetType      the target type, not necessarily the same as the method
     *                        parameter type, e.g. for {@code HttpEntity<String>}.
     * @param converterType   the selected converter type
     * @return whether this interceptor should be invoked or not
     */
    @Override
    public boolean supports(@NonNull MethodParameter methodParameter, @NonNull Type targetType,
                            @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
        log.debug("RequestBodyAdvice.supports");

        return OpenApiResult.class.getTypeName().equals(targetType.getTypeName());
    }

    /**
     * Invoked second before the request body is read and converted.
     *
     * @param inputMessage  the request
     * @param parameter     the target method parameter
     * @param targetType    the target type, not necessarily the same as the method
     *                      parameter type, e.g. for {@code HttpEntity<String>}.
     * @param converterType the converter used to deserialize the body
     * @return the input request or a new instance (never {@code null})
     */
    @NonNull
    @Override
    public HttpInputMessage beforeBodyRead(@NonNull HttpInputMessage inputMessage, MethodParameter parameter,
                                           Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        log.debug("RequestBodyAdvice.beforeBodyRead");
        return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
    }

    /**
     * Invoked third (and last) after the request body is converted to an Object.
     *
     * @param body          set to the converter Object before the first advice is called
     * @param inputMessage  the request
     * @param parameter     the target method parameter
     * @param targetType    the target type, not necessarily the same as the method
     *                      parameter type, e.g. for {@code HttpEntity<String>}.
     * @param converterType the converter used to deserialize the body
     * @return the same body or a new instance
     */
    @NonNull
    @Override
    public Object afterBodyRead(@NonNull Object body, HttpInputMessage inputMessage, MethodParameter parameter,
                                Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        log.debug("RequestBodyAdvice.afterBodyRead");
        return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
    }

    /**
     * Invoked second (and last) if the body is empty.
     *
     * @param body          usually set to {@code null} before the first advice is called
     * @param inputMessage  the request
     * @param parameter     the method parameter
     * @param targetType    the target type, not necessarily the same as the method
     *                      parameter type, e.g. for {@code HttpEntity<String>}.
     * @param converterType the selected converter type
     * @return the value to use, or {@code null} which may then raise an
     * {@code HttpMessageNotReadableException} if the argument is required
     */
    @Nullable
    @Override
    public Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
                                  Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        log.debug("RequestBodyAdvice.handleEmptyBody");
        return super.handleEmptyBody(body, inputMessage, parameter, targetType, converterType);
    }

    //endregion

    //region implements ResponseBodyAdvice<Object>

    /**
     * 1、当且仅当本方法返回 true 时，下面的 beforeBodyWrite 方法才会执行。
     * 2、可以直接返回 true，此时全部经过下面的 beforeBodyWrite 方法。如下演示的是自己包下面的控制器方法返回时才进行增强。
     *
     * @param returnType：返回类型
     * @param converterType：转换器
     * @return 返回 true 则下面的 beforeBodyWrite  执行，否则不执行
     */
    @Override
    public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
        log.debug("ResponseBodyAdvice.supports");
        // open-api模块，需要声明一个属性，控制是否对响应内容进行重新包装
        return OpenApiResult.class.equals(returnType.getParameterType()); // 是否返回 HttpsResult 类型
    }

    /**
     * 对返回的数据统一组装成 {@link com.chframework.boot.core.response.result.HttpsResult} 格式
     *
     * @param body：响应对象(response)中的响应体
     * @param returnType：控制器方法的返回类型
     * @param selectedContentType：通过内容协商选择的内容类型
     * @param selectedConverterType：选择写入响应的转换器类型
     * @param request：当前请求
     * @param response：当前响应
     * @return ：返回传入的主体或修改过的(可能是新的)主体
     */
    @Nullable
    @Override
    public OpenApiResult beforeBodyWrite(@Nullable OpenApiResult body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType,
                                       @NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                       @NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response) {
        log.debug("ResponseBodyAdvice.beforeBodyWrite");

        if (Objects.isNull(body)) {
            return OpenApiResult.createResult(ApiResult.build().setStatus(ApiStatusEnum.ERR_B0001));
        }

        OpenApiResult openApiResult = null;
        if (request instanceof ServletServerHttpRequest) {
            HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
            Object encryptKey = servletRequest.getAttribute(ApplicationConstant.REQ_ATTR_ENCRYPT_KEY);
            openApiResult = body.encrypt(String.valueOf(encryptKey));
        }

        if (Objects.isNull(openApiResult)) {
            return OpenApiResult.createResult(ApiResult.build().setStatus(ApiStatusEnum.ERR_B0001));
        }

        return openApiResult;
    }

    //endregion

}
